//
//  DYDownloadSchedule.m
//  DYFTManager
//
//  Created by 吴新庭 on 2019/5/15.
//

#import "DYDownloadSchedule.h"
#import "DYDownload.h"
#import "DYDownload+Schedule.h"
#import "DYDownloadConfig+Schedule.h"
#import <AFNetworking/AFNetworkReachabilityManager.h>
#import <Reachability/Reachability.h>

static NSInteger kMaxProcessing = 5;        // 最大同时下载数量
static NSInteger kWatchDogInterval = 60;    // 60秒做一次检查
@interface DYDownloadSchedule () <NSURLSessionDownloadDelegate>

@property (nonatomic, strong) NSURLSession *session;

@property (nonatomic, strong) NSMutableArray<DYDownload *> *waitingQueue;
@property (nonatomic, strong) NSMutableArray<DYDownload *> *processingQueue;
@property (nonatomic, strong) NSMutableArray<DYDownload *> *suspendQueue;
@property (nonatomic, strong) NSMutableArray<DYDownload *> *retryQueue;

@property (nonatomic, strong) NSMutableDictionary<NSNumber *, DYDownload *> *taskDownloadMap;

/// 队列操作的锁
@property (nonatomic, strong) dispatch_semaphore_t arrayLock;

@end

@implementation DYDownloadSchedule

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static DYDownloadSchedule *inst;
    dispatch_once(&onceToken, ^{
        inst = [[DYDownloadSchedule alloc] init];
    });
    return inst;
}

- (instancetype)init {
    if (self = [super init]) {
        _max = kMaxProcessing;
        _arrayLock = dispatch_semaphore_create(1);
        
        self.waitingQueue = [NSMutableArray array];
        self.processingQueue = [NSMutableArray array];
        self.suspendQueue = [NSMutableArray array];
        self.retryQueue = [NSMutableArray array];
        self.taskDownloadMap = [NSMutableDictionary dictionary];
        
        _max = NSUIntegerMax;
        
        [[AFNetworkReachabilityManager sharedManager] startMonitoring];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChanged:) name:kReachabilityChangedNotification object: nil];
    }
    return self;
}

- (DYDownload *)downloadForKey:(NSString *)key {
    NSArray<DYDownload *> *downloads = self.taskDownloadMap.allValues;
    NSInteger index = [downloads indexOfObjectPassingTest:^BOOL(DYDownload * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        return [obj.config.key isEqualToString:key];
    }];
    return index == NSNotFound ? nil : downloads[index];
}

- (void)cleanDownload:(DYDownload *)download {
    [self.taskDownloadMap removeObjectForKey:@(download.task.taskIdentifier)];
}

- (void)schedule:(DYDownload *)download {
    if ([self.waitingQueue containsObject:download]) {
        // 有相同的任务
        return;
    }
    
    if (NSNotFound != [self.waitingQueue indexOfObjectPassingTest:^BOOL(DYDownload * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        return [download.config.path isEqualToString:obj.config.path];
    }]) {
        // 有相同下载路径的任务
        return;
    }
    
    NSInteger pos = [self.waitingQueue indexOfObjectPassingTest:^BOOL(DYDownload * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        return obj.config.priority < download.config.priority;
    }];
    if (pos == NSNotFound) {
        [self.waitingQueue addObject:download];
    } else {
        [self.waitingQueue insertObject:download atIndex:pos];
    }
    download.state = DYDownloadStateWaiting;
    
    // 有新的任务，发起一次调度
    [self schedule];
}

- (void)suspend:(DYDownload *)download {
    if (download.task.state != NSURLSessionTaskStateRunning) {
        return;
    }
    if ([self.processingQueue indexOfObject:download] == NSNotFound) {
        return;
    }
    
    [download.task suspend];
    [self.suspendQueue addObject:download];
    [self.processingQueue removeObject:download];
    download.state = DYDownloadStateSuspend;
}

- (void)resume:(DYDownload *)download {
    if (download.task.state != NSURLSessionTaskStateSuspended) {
        return;
    }
    
    if ([self.suspendQueue indexOfObject:download] == NSNotFound) {
        return;
    }
    
    [download.task resume];
    [self.suspendQueue removeObject:download];
    [self.processingQueue addObject:download];
    download.state = DYDownloadStateProcessing;
}

- (void)cancel:(DYDownload *)download {
    if ([self.waitingQueue indexOfObject:download] != NSNotFound) {
        [self.waitingQueue removeObject:download];
    } else if ([self.processingQueue indexOfObject:download] != NSNotFound) {
        [download.task cancel];
        [self.processingQueue removeObject:download];
    } else if ([self.suspendQueue indexOfObject:download] != NSNotFound) {
        [download.task cancel];
        [self.suspendQueue removeObject:download];
    } else {}
    [self.taskDownloadMap removeObjectForKey:@(download.task.taskIdentifier)];
    download.state = DYDownloadStateCanceld;
}

#pragma mark - Private
- (void)schedule {
    // 如果网络不可达、则不调度
    BOOL isReachable = [[AFNetworkReachabilityManager sharedManager] isReachable];
    isReachable |= [[Reachability reachabilityForInternetConnection] isReachable];
    
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(schedule) object:nil];
    if (!isReachable) {
        // 启动看门狗，定时去检查一下能否调度
        [self performSelector:@selector(schedule) withObject:nil afterDelay:kWatchDogInterval];
        return;
    }
    
    if (self.waitingQueue.count == 0) {
        // 没有需要调度的任务
        return;
    }
    
    if (self.processingQueue.count >= self.max) {
        // 达到最大并行数量
        return;
    }
    
    dispatch_semaphore_wait(self.arrayLock, DISPATCH_TIME_FOREVER);
    DYDownload *download = self.waitingQueue.firstObject;
    if (download) {
        NSMutableURLRequest *mRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:download.config.path]];
        mRequest.timeoutInterval = download.config.timeout;
        
        NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:mRequest];
        download.task = task;
        [self.taskDownloadMap setObject:download forKey:@(task.taskIdentifier)];
        [task resume];
        
        [self.processingQueue addObject:download];
        if (self.waitingQueue.count > 0) {
            [self.waitingQueue removeObjectAtIndex:0];
        }
        download.state = DYDownloadStateProcessing;
    }
    dispatch_semaphore_signal(self.arrayLock);
    
    // 继续
    [self schedule];
}

- (NSURLSession *)session
{
    if (!_session) {
        NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
        self.session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

#pragma mark - Action
- (void)networkChanged:(NSNotification *)noti {
    // 网络变化为可达，发起调度
    if ([[AFNetworkReachabilityManager sharedManager] isReachable]) {
        [self schedule];
    }
}

#pragma mark - Delegate
/// 进行中
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    DYDownload *download = [self.taskDownloadMap objectForKey:@(downloadTask.taskIdentifier)];
    download.progress.totalUnitCount = totalBytesExpectedToWrite;
    download.progress.completedUnitCount = totalBytesWritten;
    download.state = DYDownloadStateProcessing;
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {
    completionHandler(request);
}

/// 再次启动
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    
}

/// 完成下载
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    DYDownload *download = [self.taskDownloadMap objectForKey:@(downloadTask.taskIdentifier)];
    
    // download.state = DYDownloadStateSuccess;
    // 转存。unzip不能直接从tmp中读流
    NSError *error;
    NSString *transit = [download.config.target stringByAppendingPathComponent:location.lastPathComponent];
    if ([[NSFileManager defaultManager] moveItemAtPath:location.path toPath:transit error:&error]) {
        download.tmpFilePath = [NSURL fileURLWithPath:transit];
    } else {
        download.error = error;
        download.state = DYDownloadStateFail;
    }
}

/// 整体完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    /// 清理
    DYDownload *download = [self.taskDownloadMap objectForKey:@(task.taskIdentifier)];
    [self.processingQueue removeObject:download];
    if (error)  {
        [download setError:error];
        download.state = DYDownloadStateFail;
        
        if (download.config.retryCount-- > 0) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(download.config.retryDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self schedule:download];
            });
        }
    }
}

@end
